<?php
/**
 * Kelas generik untuk manajemen data pada sebuah tabel.
 *
 * Kelas ini menyediakan fitur scaffolding sederhana yang dapat dioverride
 * dengan meng-extend kelas ini.
 *
 * @category   Controllers
 * @package    Generic
 * @author     Lie Roberto Eliantono <roberto.ea@gmail.com>
 * @version    0.2.2
 */
class Gnc_mng extends MX_Controller
{
	protected $model_path	= 'gnc_db_mysql';
	protected $detail_model_path	= NULL;	
	protected $view_attr	= NULL;			  
	protected $add_attr		= NULL;
	protected $update_attr	= NULL;
	protected $export_attr	= NULL;
	protected $import_attr	= NULL;
	protected $browse_attr	= NULL;
	
	/**
	 * Menginisialisasi atribut kelas dengan default values
	 *
	 * @return void
	 * @access public
	 */
	public function __construct() {
		parent::__construct();
	
		if (is_null($this->view_attr)) {
			$this->view_attr	= array(
								'single' 	=> array(
												'layout'	=> 'layout/default',
												'title'		=> 'View Record',
												'nav'		=> 'nav/gnc_mng_view_single',
												'content'	=> 'viewer/columnar',
												'function'	=> 'view',
											   ),
								'multiple' 	=> array(
												'layout'	=> 'layout/default',
												'title'		=> 'View Records',
												'nav'		=> 'nav/gnc_mng_view_multiple',
												'content'	=> 'viewer/tabular',
												'function'	=> 'view',
												'records_per_page' => $this->config->item('table_records_per_page'),
											   ),
								'master_detail' 	=> array(
												'layout'	=> 'layout/default',
												'title'		=> 'View Master-Detail Record',
												'nav'		=> 'nav/gnc_mng_view_master_detail',
												'content'	=> 'viewer/master_detail',
												'function'	=> 'view',
												'records_per_page' => $this->config->item('table_records_per_page'),
											   ),
								'single_master' 	=> array(
												'layout'	=> 'layout/default',
												'title'		=> 'View Master Record',
												'nav'		=> 'nav/gnc_mng_view_single_detail',
												'content'	=> 'viewer/columnar',
												'function'	=> 'view/type/master',
											   ),	
							    'single_detail' 	=> array(
												'layout'	=> 'layout/default',
												'title'		=> 'View Detail Record',
												'nav'		=> 'nav/gnc_mng_view_single_detail',
												'content'	=> 'viewer/columnar',
												'function'	=> 'view/type/detail',
											   ),											   
							  );
		}
		
		if (is_null($this->add_attr)) {
			$this->add_attr		= array(
								'single' 	=> array(
												'layout'	=> 'layout/default',
												'title'		=> 'Add Record',
												'nav'		=> 'nav/gnc_mng_add_single',
												'content'	=> 'form/columnar',
												'function'	=> 'add',
											   ),
								'single_master' 	=> array(
												'layout'	=> 'layout/default',
												'title'		=> 'Add Master Record',
												'nav'		=> 'nav/gnc_mng_add_single',
												'content'	=> 'form/columnar',
												'function'	=> 'add/type/master',
											   ),
								'single_detail' 	=> array(
												'layout'	=> 'layout/default',
												'title'		=> 'Add Detail Record',
												'nav'		=> 'nav/gnc_mng_add_single',
												'content'	=> 'form/columnar',
												'function'	=> 'add/type/detail',
											   ),
							  );
		}
		
		if (is_null($this->update_attr)) {
			$this->update_attr	= array(
								'single' 	=> array(
												'layout'	=> 'layout/default',
												'title'		=> 'Update Record',
												'nav'		=> 'nav/gnc_mng_update_single',
												'content'	=> 'form/columnar',
												'function'	=> 'update',
											   ),
								'single_master' 	=> array(
												'layout'	=> 'layout/default',
												'title'		=> 'Update Master Record',
												'nav'		=> 'nav/gnc_mng_update_single',
												'content'	=> 'form/columnar',
												'function'	=> 'update/type/master',
											   ),
								'single_detail' 	=> array(
												'layout'	=> 'layout/default',
												'title'		=> 'Update Detail Record',
												'nav'		=> 'nav/gnc_mng_update_single',
												'content'	=> 'form/columnar',
												'function'	=> 'update/type/detail',
											   ),
							  );
		}
		
		if (is_null($this->delete_attr)) {
			$this->delete_attr	= array(
								'single' 		=> array(
												'title'		=> 'Delete Record',
												'function'	=> 'delete',
											   ),
								'single_master' => array(
												'title'		=> 'Delete Master Record',
												'function'	=> 'delete/type/master',
											   ),
								'single_detail' => array(
												'title'		=> 'Delete Detail Record',
												'function'	=> 'delete/type/detail',
											   ),											   
								'multiple' 		=> array(
												'title'		=> 'Delete Records',
												'function'	=> 'delete/type/bulk',
											   ),
							  );
		}
		
		if (is_null($this->export_attr)) {
			$this->export_attr	= array(
								'layout'	=> 'layout/default',
								'title'		=> 'Export Data',
								'nav'		=> 'nav/gnc_mng_export',
								'function'	=> 'export',
							  );
		}
		
		if (is_null($this->import_attr)) {
			$this->import_attr	= array(
								'layout'	=> 'layout/default',
								'title'		=> 'Import Data',
								'content'	=> 'form/import',
								'nav'		=> 'nav/gnc_mng_import',
								'function'	=> 'import',
							  );		
		}
		
		if (is_null($this->browse_attr)) {
			$this->browse_attr	= array(
								'layout'	=> 'layout/popup',
								'title'		=> 'Browse Data',
								'content'	=> 'viewer/tabular',								
								'title'		=> 'Browse Records',
								'function'	=> 'browse',
								'records_per_page' => $this->config->item('table_records_per_page'),
							  );		
		}
		
		if (!is_null($this->model_path)) {
			$this->load->model($this->model_path, 'model');
		}
		
		if (!is_null($this->detail_model_path)) {
			$this->load->model($this->detail_model_path, 'detail_model');
		}
	}
	
	/**
	 * Mengeset variabel $this->$name dengan $value
	 *
	 * @return void
	 * @access public
	 */
	public function __set($name, $value) {
		$this->$name = $value;
	}
	
	/**
	 * Mengambil isi dari variabel $this->$name
	 *
	 * @return mixed nilai dari $this->$name
	 * @access public
	 */
	public function __get($name) {
		$result = parent::__get($name);
		if (is_null($result)) {
			$result = $this->$name;
		}
		return $result;
	}
	
	/**
	 * Menentukan method yang dipanggil ketika controller diakses
	 *
	 * Secara default, method ini akan melakukan redirection ke method view,
	 * untuk menampilkan keseluruhan data pada tabel.
	 *
	 * @return void
	 * @access public
	 * @see view()
	 */
	public function index() {
		redirect(get_unit_controller($this).'/'.$this->view_attr['multiple']['function']);
	}
	
	
	
	/**
	 * Mengisi data navigasi
	 *
	 * Method ini akan mengambil setting untuk navigasi dan menyimpannya di variabel $nav_data
	 *
	 * @param mixed $nav_data		berisikan data terkait navigasi
	 * @param mixed	$external_data	data eksternal yang dapat dimanfaatkan oleh method	 
	 *
	 * @return void
	 * @access protected
	 */
	protected function populate_nav_data(&$nav_data = NULL, &$external_data = NULL) {
		if (is_null($nav_data)) $nav_data = array();
	
		$action_list = array('view', 'add', 'update', 'delete');
		$mode_list = array('single', 'single_master', 'single_detail', 'multiple', 'master_detail');
		$type_list = array('title', 'function');
		
		// convert all $this->{action}_attr[{mode}][{type}] into $nav_data[{action}_{mode}_{type}]
		foreach ($action_list as $action) {
			foreach ($mode_list as $mode) {
				foreach ($type_list as $type) {
					$idx = $action.'_'.$mode.'_'.$type;
					$attrvar = $action.'_attr';
					if (isset($this->{$attrvar}[$mode][$type])) {
						$nav_data[$idx] = $this->{$attrvar}[$mode][$type];
					}
				}
			}
		}
	}
	





	/**
	 * Mengambil data dari model dan menampilkannya
	 *
	 * Data yang ditampilkan dapat disaring sesuai kriteria melalui parameter.
	 *
	 * @param array $args array yang berisi kriteria data yang akan di-view.
	 *                    Isi array didapat dengan memproses segmen2 dari
	 *                    URI, dengan format:
	 *                      record/$id
	 *                      record/$key
	 *                              key dimasukkan dalam bentuk ter-encode
	 *						record/with_keys/$encoded_key/$encoded_value
	 *                      criteria/$criteria
	 *                                criteria dalam bentuk SQL-like statement
	 *                                dalam bentuk ter-encode
	 *                      from/$from
	 *                      count/$n
	 *				khusus untuk data tabular:
	 *						sort_asc_by
	 *						sort_desc_by
	 *						page
	 *						filter
	 *				khusus untuk master-detail:
	 *						type
	 *                    
	 * @return void
	 * @access public
	 */
	public function view() {
		$view_data = NULL;
		$this->view_input($view_data);
		$this->view_process($view_data);
		$this->view_output($view_data);
	}	

	/**
	 * Memproses input yang diterima controller ketika method view dipanggil
	 *
	 * Method mengambil data yang di-passing via URI maupun via $_POST
	 *
	 * @param	mixed $view_data array yang berisi data yang dipakai untuk view	 
	 *                    
	 * @return void
	 * @access protected
	 */	
	protected function view_input(&$view_data = NULL) {
		if (is_null($view_data)) $view_data = array();		
		$view_data['options'] = $this->common->get_options_from_uri();
		if ($this->input->post('_criteria')) {
			$view_data['criteria'] = $this->input->post('_criteria');
		}
	}
		
	/**
	 * Mengisi data navigasi untuk view
	 *
	 * Method ini akan mengambil setting untuk navigasi dan menyimpannya di variabel $nav_data
	 *
	 * @param mixed $nav_data		berisikan data terkait navigasi
	 * @param mixed	$external_data	data eksternal yang dapat dimanfaatkan oleh method	 
	 *
	 * @return void
	 * @access protected
	 */
	protected function populate_view_nav_data(&$nav_data = NULL, &$external_data = NULL) {
		$this->populate_nav_data($nav_data, $external_data);
	}
	
	/**
	 * Mengisi data content
	 *
	 * Method ini akan mengambil data record dari database untuk ditampilkan serta data pendukung lainnya
	 * dan menyimpannya di variabel $content_data
	 *
	 * @param mixed $content_data		berisikan data terkait content
	 * @param mixed	$external_data	data eksternal yang dapat dimanfaatkan oleh method	 
	 *
	 * @return void
	 * @access protected
	 */
	protected function populate_view_content_data(&$content_data = NULL, &$external_data = NULL) {
		$mode = $external_data['view_data']['mode'];		
			
		if (strcasecmp($mode, 'single')==0) {
			$data = $this->view_single_content_data($content_data, $external_data);
		} else if (strcasecmp($mode, 'multiple')==0) {
			$data = $this->view_multiple_content_data($content_data, $external_data);
		} else if (strcasecmp($mode, 'master_detail')==0) {
			$data = $this->view_master_detail_content_data($content_data, $external_data);
		}

		$nav = $external_data['view_data']['nav_data'];		
		$options = $external_data['view_data']['options'];
		
		$this->populate_view_actions($actions, $_ = array('mode' => $mode, 'content_data' => compact('nav', 'data', 'options', 'actions')));
		
		$content_data = compact('nav', 'options', 'data', 'actions');
	}
	
	protected function view_multiple_content_data(&$content_data = NULL, &$external_data = NULL) {
		$mode = $external_data['view_data']['mode'];		
	
		if (isset($external_data['view_data']['options']['criteria'])) {
			$where = $external_data['view_data']['options']['criteria'];
		} else {
			$where = NULL;
		}
		
		if (isset($external_data['view_data']['options']['filter'])) {
			$filter = array();
			foreach ($external_data['view_data']['options']['filter'] as $idx => $val) {
				$vp = explode(" ", $val);
				if (in_arrayi($vp[0], array('=', '>', '>=', '<', '<=', 'LIKE'))) {
					$operator = $vp[0];
					unset($vp[0]);
					$vi = implode(" ", $vp);
					$filter[$idx.' '.$operator] = $vi;
				} else {
					$filter[$idx.' LIKE'] = $val.'%';
				}
			}
		
			if (!is_array($where)) {
				$where = array();
			}
			$where = array_merge($where, $filter);
			if (is_empty($where)) {
				$where = NULL;
			}
		}
		
		
		$records_per_page = $this->view_attr[$mode]['records_per_page'];
		if (is_null_or_false($external_data['view_data']['options']['page'])) {
			$external_data['view_data']['options']['page'] = 1;
		}
		$curr_page = $external_data['view_data']['options']['page'];			
		
		if (isset($external_data['view_data']['options']['order_by']['field']) && isset($external_data['view_data']['options']['order_by']['type'])) {
			$order_by = array($external_data['view_data']['options']['order_by']['field'] => $external_data['view_data']['options']['order_by']['type']);
		} else {
			$order_by = NULL;
		}
		
		$limit = $records_per_page;
		if (is_null_or_false($limit)) $limit = 0;
		
		if (isset($external_data['view_data']['options']['count'])) $limit = $external_data['view_data']['options']['count'];
		
		$offset = ($curr_page-1)*$limit;
		if (isset($external_data['view_data']['options']['from'])) {
			$start = $external_data['view_data']['options']['from'];
			$offset += $start;
		} else {
			$start = 0;
		}
				
		$data = $this->model->get_from_query(NULL, $where, array('offset' => $offset, 'limit' => $limit), $order_by);
		$n_records = $data['data']['n_all_records']-$start;			
		if ($limit>0) {
			$n_page = ceil($n_records/$limit);
		} else {
			$n_page = 1;
		}
		if ($n_page<1) $n_page = 1;
		$external_data['view_data']['options']['n_page'] = $n_page;

		return $data;
	}
	
	protected function view_single_content_data(&$content_data = NULL, &$external_data = NULL) {
		$mode = $external_data['view_data']['mode'];		
		
		$data = $this->model->get_single($external_data['view_data']['options']['keys']);
		// jika current = empty
		if (is_null_or_false($data['data']['current'])) {
			// if previous != empty, current = previous
			if ($data['data']['previous']) {
				$data = $this->model->get_single(array('id' => $data['data']['previous']['id']));
			}  else if ($data['data']['next']) {	// else current = next
				$data = $this->model->get_single(array('id' => $data['data']['next']['id']));
			}  else if ($data['data']['first']) {	// else current = first
				$data = $this->model->get_single(array('id' => $data['data']['first']['id']));
			}  else if ($data['data']['last']) {	// else current = last
				$data = $this->model->get_single(array('id' => $data['data']['last']['id']));
			}
		}
		
		return $data;
	}
	
	protected function view_master_detail_content_data(&$content_data = NULL, &$external_data = NULL) {
		$mode = $external_data['view_data']['mode'];		
		
		if (isset($external_data['view_data']['options']['type']) && (strcasecmp($external_data['view_data']['options']['type'], 'detail')==0)) {
			$detail_data = $this->detail_model->get_single($external_data['view_data']['options']['keys']);
			// jika current = empty
			if (is_null_or_false($detail_data['data']['current'])) {
				// if previous != empty, current = previous
				if ($detail_data['data']['previous']) {
					$detail_data = $this->detail_model->get_single(array('id' => $detail_data['data']['previous']['id']));
				}  else if ($detail_data['data']['next']) {	// else current = next
					$detail_data = $this->detail_model->get_single(array('id' => $detail_data['data']['next']['id']));
				}  else if ($detail_data['data']['first']) {	// else current = first
					$detail_data = $this->detail_model->get_single(array('id' => $detail_data['data']['first']['id']));
				}  else if ($detail_data['data']['last']) {	// else current = last
					$detail_data = $this->detail_model->get_single(array('id' => $detail_data['data']['last']['id']));
				}
			}
			
			$metadata = $this->model->get_metadata_from_query();
			
			$record = array();
			$fk_list = array();
			foreach ($detail_data['metadata']['keys']['foreign'][$detail_data['metadata']['pri_table']] as $fki => $fkv) {
				if (strcasecmp($fkv['fk_table'], $metadata['pri_table'])==0) {
					if (!is_array($fkv['value'])) {
						$fkv['value'] = array($fkv['value']);
					}
					foreach ($fkv['value'] as $fkv_value) {
						$record[$fkv['fk_value'][$fkv_value]] = $detail_data['data']['current'][$fkv_value];
						$fk_list[] = $fkv['fk_value'][$fkv_value];
					}
				}
			}
			
			$master_data = $this->model->get_single($record);
			
			$data = array('master' => $master_data, 'detail' => $detail_data);
			
		} else if (isset($external_data['view_data']['options']['keys'])) {
			$master_data = $this->model->get_single($external_data['view_data']['options']['keys']);
			// jika current = empty
			if (is_null_or_false($master_data['data']['current'])) {
				// if previous != empty, current = previous
				if ($master_data['data']['previous']) {
					$master_data = $this->model->get_single(array('id' => $master_data['data']['previous']['id']));
				}  else if ($master_data['data']['next']) {	// else current = next
					$master_data = $this->model->get_single(array('id' => $master_data['data']['next']['id']));
				}  else if ($master_data['data']['first']) {	// else current = first
					$master_data = $this->model->get_single(array('id' => $master_data['data']['first']['id']));
				}  else if ($master_data['data']['last']) {	// else current = last
					$master_data = $this->model->get_single(array('id' => $master_data['data']['last']['id']));
				}
			}
					
			$metadata = $this->detail_model->get_metadata_from_query();
			
			$record = array();
			foreach ($metadata['keys']['foreign'][$metadata['pri_table']] as $fki => $fkv) {
				if (strcasecmp($fkv['fk_table'], $master_data['metadata']['pri_table'])==0) {
					if (!is_array($fkv['value'])) {
						$fkv['value'] = array($fkv['value']);
					}
					foreach ($fkv['value'] as $fkv_value) {
						$record[$fkv_value] = $master_data['data']['current'][$fkv['fk_value'][$fkv_value]];
					}
				}
			}
			
			if (isset($external_data['view_data']['options']['criteria'])) {
				$where = array_merge($record, $external_data['view_data']['options']['criteria']);
			} else {
				$where = $record;
			}
			
			if (isset($external_data['view_data']['options']['filter'])) {
				$filter = array();
				foreach ($external_data['view_data']['options']['filter'] as $idx => $val) {
					$vp = explode(" ", $val);
					if (in_arrayi($vp[0], array('=', '>', '>=', '<', '<=', 'LIKE'))) {
						$operator = $vp[0];
						unset($vp[0]);
						$vi = implode(" ", $vp);
						$filter[$idx.' '.$operator] = $vi;
					} else {
						$filter[$idx.' LIKE'] = $val.'%';
					}
				}
			
				if (!is_array($where)) {
					$where = array();
				}
				$where = array_merge($where, $filter);
				if (is_empty($where)) {
					$where = NULL;
				}
			}
			
			
			$records_per_page = $this->view_attr[$mode]['records_per_page'];
			if (is_null_or_false($external_data['view_data']['options']['page'])) {
				$external_data['view_data']['options']['page'] = 1;
			}
			$curr_page = $external_data['view_data']['options']['page'];	
			
			if (isset($external_data['view_data']['options']['order_by']['field']) && isset($external_data['view_data']['options']['order_by']['type'])) {
				$order_by = array($external_data['view_data']['options']['order_by']['field'] => $external_data['view_data']['options']['order_by']['type']);
			} else {
				$order_by = NULL;
			}
			
			$limit = $records_per_page;
			if (is_null_or_false($limit)) $limit = 0;
			
			if (isset($external_data['view_data']['options']['count'])) $limit = $external_data['view_data']['options']['count'];
			
			$offset = ($curr_page-1)*$limit;
			if (isset($external_data['view_data']['options']['from'])) {
				$start = $external_data['view_data']['options']['from'];
				$offset += $start;
			} else {
				$start = 0;
			}

			$detail_data = $this->detail_model->get_from_query(NULL, $where, array('offset' => $offset, 'limit' => $limit), $order_by);
			$n_records = $detail_data['data']['n_all_records']-$start;			
			if ($limit>0) {
				$n_page = ceil($n_records/$limit);
			} else {
				$n_page = 1;
			}
			if ($n_page<1) $n_page = 1;
			$external_data['view_data']['options']['n_page'] = $n_page;
	
			//$detail_data = $this->detail_model->get_from_query(NULL, $record);
			
			$data = array('master' => $master_data, 'detail' => $detail_data);
		} else {
			$data = array('master' => $this->view_multiple_content_data($content_data, $external_data));
		}

		return $data;
	}
	
	/**
	 * Mengeset mode view
	 *
	 * Method ini akan menentukan mode viewer (single/columnar, multiple/tabular, atau master-detail)
	 * berdasarkan data option yang telah diterima di method view_input
	 *
	 * @param mixed $mode		berisikan data terkait mode
	 * @param mixed	$external_data	data eksternal yang dapat dimanfaatkan oleh method	 
	 *
	 * @return void
	 * @access protected
	 * @see view_input()
	 */
	protected function populate_view_mode(&$mode = NULL, &$external_data = NULL) {
		if (isset($this->detail_model_path)) {
			$mode = 'master_detail';
		} else if (isset($external_data['view_data']['options']['keys'])) {
			$mode = 'single';
		} else {
			$mode = 'multiple';
		}
	}
	
	/**
	 * Mengeset aksi untuk view multiple
	 *
	 * Method ini mengisi aksi-aksi yang diperbolehkan saat viewer tabular atau master-detail di-view
	 *
	 * @param mixed	$external_data	data eksternal yang dapat dimanfaatkan oleh method	 
	 *
	 * @return array data aksi yang tersedia
	 * @access protected
	 */
	protected function view_multiple_actions(&$external_data = NULL) {
		if (strcasecmp($external_data['mode'], 'master_detail')==0) {
			if (isset($external_data['content_data']['options']['keys'])) {
				$metadata = $external_data['content_data']['data']['detail']['metadata'];
				$data = $external_data['content_data']['data']['detail']['data'];
				$modifier = 'detail_';
			} else {
				$metadata = $external_data['content_data']['data']['master']['metadata'];
				$data = $external_data['content_data']['data']['master']['data'];
				$modifier = 'master_';
			}
		} else {
			$metadata = $external_data['content_data']['data']['metadata'];
			$data = $external_data['content_data']['data']['data'];
			$modifier = '';
		}
	
		$left = array();
		$center = array();
		$right = array();
		
		$left[] = $this->common->anchor(get_unit_controller($this).'/'.$external_data['content_data']['nav']['view_multiple_function'].'/{FIRST_PAGE}', '{LABEL_FIRST}').$this->common->anchor(get_unit_controller($this).'/'.$external_data['content_data']['nav']['view_multiple_function'].'/{PREV_PAGE}', '{LABEL_PREV}');
		
		$center[] = '{CURR_PAGE} of {N_PAGE}';
		
		$right[] = $this->common->anchor(get_unit_controller($this).'/'.$external_data['content_data']['nav']['view_multiple_function'].'/{NEXT_PAGE}', '{LABEL_NEXT}').$this->common->anchor(get_unit_controller($this).'/'.$external_data['content_data']['nav']['view_multiple_function'].'/{LAST_PAGE}', '{LABEL_LAST}');
		
		$nav = compact('left', 'center', 'right');
		
		$left = array();
		$right = array();
		
		if ($data['n_records']>0) {
			foreach ($data['records'] as $ridx => $rval) {
				$left[$ridx][] = $this->common->anchor(get_unit_controller($this).'/'.$external_data['content_data']['nav']['update_single_'.$modifier.'function'].'/{RECORD}', '{LABEL_UPDATE}', '', '{LABEL_DISABLED_UPDATE}');
				$right[$ridx][] = $this->common->anchor(get_unit_controller($this).'/'.$external_data['content_data']['nav']['view_single_'.$modifier.'function'].'/{RECORD}', '{LABEL_VIEW}', '', '{LABEL_DISABLED_VIEW}')._MED_SEP.$this->common->anchor(get_unit_controller($this).'/'.$external_data['content_data']['nav']['delete_single_'.$modifier.'function'].'/{RECORD}', '{LABEL_DELETE}', array('onclick' => 'return confirmDelete();'), '{LABEL_DISABLED_DELETE}');
			}

			// bulk actions
			$bulk = array();
			$bulk['list'] = array();
			// example
			/*$bulk['processor'] = '';
			$bulk['list'][] = form_submit('__submit_'.$external_data['content_data']['nav']['delete_multiple_function'], $external_data['content_data']['nav']['delete_multiple_title']);*/

			// in_place actions
			$in_place = array();
			// example			
			/*$in_place[1][1] = anchor('test', '{VALUE}');*/
		}
		
		$actions = compact('left', 'right', 'bulk', 'in_place', 'nav');

		return $actions;
	}
	
	/**
	 * Mengeset aksi untuk view single
	 *
	 * Method ini mengisi aksi-aksi yang diperbolehkan saat viewer columnar atau master-detail di-view
	 *
	 * @param mixed	$external_data	data eksternal yang dapat dimanfaatkan oleh method	 
	 *
	 * @return array data aksi yang tersedia
	 * @access protected
	 */
	protected function view_single_actions(&$external_data = NULL) {
		if (strcasecmp($external_data['mode'], 'master_detail')==0) {
			$options = $external_data['content_data']['options'];
			if (isset($options['type']) && (strcasecmp($options['type'], 'detail')==0)) {
				$modifier = 'detail_';
			} else {
				$modifier = 'master_';
			}
		} else {
			$modifier = '';
		}
	
		$left = array();
		$center = array();
		$right = array();
		
		$left[] = $this->common->anchor(get_unit_controller($this).'/'.$external_data['content_data']['nav']['delete_single_'.$modifier.'function'].'/{RECORD}', '{LABEL_DELETE}', array('onclick' => 'return confirmDelete();'), '{LABEL_DISABLED_DELETE}');
		$left[] = $this->common->anchor(get_unit_controller($this).'/'.$external_data['content_data']['nav']['view_single_'.$modifier.'function'].'/{FIRST_RECORD}', '{LABEL_FIRST}').$this->common->anchor(get_unit_controller($this).'/'.$external_data['content_data']['nav']['view_single_'.$modifier.'function'].'/{PREV_RECORD}', '{LABEL_PREV}');
		
		$center[] = '{RECORD_ID}';
		
		$right[] = $this->common->anchor(get_unit_controller($this).'/'.$external_data['content_data']['nav']['view_single_'.$modifier.'function'].'/{NEXT_RECORD}', '{LABEL_NEXT}').$this->common->anchor(get_unit_controller($this).'/'.$external_data['content_data']['nav']['view_single_'.$modifier.'function'].'/{LAST_RECORD}', '{LABEL_LAST}');
		$right[] = $this->common->anchor(get_unit_controller($this).'/'.$external_data['content_data']['nav']['update_single_'.$modifier.'function'].'/{RECORD}', '{LABEL_UPDATE}', '', '{LABEL_DISABLED_UPDATE}');
		
		$nav = compact('left', 'center', 'right');
		
		//TODO: in_place actions
		$in_place = array();		
		
		$actions = compact('nav', 'in_place');
		
		return $actions;
	}
	
	/**
	 * Mengeset aksi untuk view master-detail
	 *
	 * Method ini mengisi aksi-aksi yang diperbolehkan saat viewer columnar master-detail di-view
	 * Teknisnya hanya menggabungkan aksi pada view tabular dan columnar
	 *
	 * @param mixed	$external_data	data eksternal yang dapat dimanfaatkan oleh method	 
	 *
	 * @return array data aksi yang tersedia
	 * @access protected
	 */
	protected function view_master_detail_actions(&$external_data = NULL) {
		$options = $external_data['content_data']['options'];
		if (isset($options['type']) && (strcasecmp($options['type'], 'detail')==0)) {
			$actions = array('detail' => $this->view_single_actions($external_data));
		} else {
			if (isset($options['keys'])) {
				$actions = array('master' => $this->view_single_actions($external_data), 'detail' => $this->view_multiple_actions($external_data));
			} else {
				$actions = array('master' => $this->view_multiple_actions($external_data));
			}
		}
		return $actions;
	}
	
	/**
	 * Mengeset aksi untuk viewer
	 *
	 * Method ini mengisi aksi-aksi yang diperbolehkan saat viewer di-view,
	 * akan memanggil method yang sesuai berdasar mode viewer
	 *
	 * @param mixed	$actions	data aksi yang tersedia
	 * @param mixed	$external_data	data eksternal yang dapat dimanfaatkan oleh method	 
	 *
	 * @access protected
	 */
	protected function populate_view_actions(&$actions = NULL, &$external_data = NULL) {
		if (strcasecmp($external_data['mode'], 'multiple')==0) {	
			$actions = $this->view_multiple_actions($external_data);
		} else if (strcasecmp($external_data['mode'], 'single')==0) {
			$actions = $this->view_single_actions($external_data);
		} else if (strcasecmp($external_data['mode'], 'master_detail')==0) {
			$actions = $this->view_master_detail_actions($external_data);
		}
	}
	
	/**
	 * Memproses data yang digunakan untuk menampilkan viewer
	 *
	 * Method menyiapkan data yang akan ditampilkan
	 *
	 * @param	mixed $view_data array yang berisi data yang dipakai untuk view	 
	 *                    
	 * @return void
	 * @access protected
	 * @see populate_view_mode(), populate_view_nav_data(), populate_view_content_data()
	 */	
	protected function view_process(&$view_data = NULL) {	
		if (is_null($view_data)) $view_data = array();
		$this->populate_view_mode($view_data['mode'], $_ = array('view_data' => $view_data));
		$this->populate_view_nav_data($view_data['nav_data']);
		$this->populate_view_content_data($view_data['content_data'], $_view_data = array('view_data' => $view_data));
		
		//print_r_pre($view_data);		
	}
	
	/**
	 * Menampilkan view
	 *
	 * Method menampilkan view yang sesuai
	 *
	 * @param	mixed $view_data array yang berisi data yang dipakai untuk view	 
	 *                    
	 * @return void
	 * @access protected
	 */	
	protected function view_output(&$view_data = NULL) {
		if (is_null($view_data)) $view_data = array();
	
		//required: $mode
		if (isset($view_data['mode'])) {
			$mode = $view_data['mode'];
		
			$application		= get_application($this);
			$application_title	= $this->common->get_application_description($application);
			$unit				= get_unit($this);
			$unit_title			= $this->common->get_unit_description($unit);
			$unit_controller	= get_unit_controller($this);
			$page 				= $this->view_attr[$mode]['title'];
			
			$metadata = compact('application', 'application_title', 'unit', 'unit_title', 'unit_controller', 'page');

			if (isset($view_data['content_data']['data']['master']['data']['current'])) {
				$view_data['nav_data']['master_record'] = $view_data['content_data']['data']['master']['data']['current'];
			}
			
			$nav_data 			= array_merge($metadata, $view_data['nav_data']);
			$content_data		= array_merge($metadata, $view_data['content_data']);
						
			$navigation			= $this->load->view($this->view_attr[$mode]['nav'], $nav_data, TRUE);
			$content			= $this->load->view($this->view_attr[$mode]['content'], $content_data, TRUE);;
		
			$data = array_merge($metadata, compact('navigation', 'content'));

			$this->load->view($this->view_attr[$mode]['layout'], $data);
		}
	}




	/**
	 * Mengentri data
	 *
	 * Data diterima dari form masukan user
	 *
	 * @return void
	 * @access public
	 */
	public function add() {
		$add_data = NULL;
		$this->add_input($add_data);
		$this->add_process($add_data);
		$this->add_output($add_data);
	}
	
	protected function add_input(&$add_data = NULL) {
		if (is_null($add_data)) $add_data = array();		
		$add_data['options'] = $this->common->get_options_from_uri();
	}
	
	protected function add_process(&$add_data = NULL) {
		if (is_null($add_data)) $add_data = array();
		$this->populate_add_mode($add_data['mode'], $_ = array('add_data' => $add_data));		
		$this->populate_add_nav_data($add_data['nav_data']);
		$this->populate_add_content_data($add_data['content_data'], $_ = array('add_data' => $add_data));	
		$this->session->set_flashdata('referrer', $this->referrer);
	}

	protected function populate_add_nav_data(&$nav_data = NULL, &$external_data = NULL) {
		$this->populate_nav_data($nav_data, $external_data);
	}
	
	protected function populate_add_content_data(&$content_data = NULL, &$external_data = NULL) {
		$mode = $external_data['add_data']['mode'];		

		$nav = $external_data['add_data']['nav_data'];

		$options = $external_data['add_data']['options'];
		
		// get metadata only
		if (isset($options['type']) && (strcasecmp($options['type'], 'detail')==0)) {
			$master_data = $this->model->get_single($options['keys']);
			$data = $this->detail_model->get_from_sql(NULL, true, false);
			$record = array();
			foreach ($data['metadata']['keys']['foreign'][$data['metadata']['pri_table']] as $fi => $fv) {
				foreach ($fv['fk_value'] as $idx => $val) {
					if (in_array($val, $master_data['metadata']['fields_list'])) {
						$record[$idx] = $master_data['data']['current'][$val];
					}
				}
			}
			$data['data']['current'] = $record;
		} else {
			$data = $this->model->get_from_sql(NULL, true, false);
		}

		$this->populate_add_actions($actions, $_ = array('mode' => $mode, 'content_data' => compact('nav', 'data', 'options', 'actions')));		

		if (isset($options['type']) && (strcasecmp($options['type'], 'detail')==0)) {
			$mode .= '_detail';
		}		
		
		$form = array('action' => get_unit_controller($this).'/process_'.$this->add_attr[$mode]['function'], 'attributes' => array());
		
		$content_data = compact('nav', 'options', 'data', 'actions', 'form');
	}
	
	protected function populate_add_mode(&$mode = NULL, &$external_data = NULL) {
		$mode = 'single';
	}
	
	protected function populate_add_actions(&$actions = NULL, &$external_data = NULL) {
		$metadata = $external_data['content_data']['data']['metadata'];
		$data = $external_data['content_data']['data']['data'];
			
		if (strcasecmp($external_data['mode'], 'single')==0) {
			$left = array();
			$center = array();
			$right = array();
			
			$center[] = '{SAVE_BUTTON}';
			
			$actions = compact('left', 'center', 'right');
		}
	}
	
	protected function add_output(&$add_data = NULL) {
		if (is_null($add_data)) $add_data = array();
	
		//required: $mode
		if (isset($add_data['mode'])) {
			$mode = $add_data['mode'];
	
			$application		= get_application($this);
			$application_title	= $this->common->get_application_description($application);
			$unit				= get_unit($this);
			$unit_title			= $this->common->get_unit_description($unit);
			$unit_controller	= get_unit_controller($this);
			$page 				= $this->add_attr[$mode]['title'];
			
			$metadata = compact('application', 'application_title', 'unit', 'unit_title', 'unit_controller', 'page');

			$nav_data 			= array_merge($metadata, $add_data['nav_data']);
			$content_data		= array_merge($metadata, $add_data['content_data']);
			
			$navigation			= $this->load->view($this->add_attr[$mode]['nav'], $nav_data, TRUE);
			$content			= $this->load->view($this->add_attr[$mode]['content'], $content_data, TRUE);;
		
			$data = array_merge($metadata, compact('navigation', 'content'));

			$this->load->view($this->add_attr[$mode]['layout'], $data);
		}
	}
	
	public function process_add() {
		$add_data = NULL;
		$this->process_add_input($add_data);
		$this->process_add_process($add_data);
		$this->process_add_output($add_data);
	}

	protected function process_add_input(&$add_data = NULL) {		
		$data = array();	
		$options = array();
		
		$add_data['options'] = $this->common->get_options_from_uri();	
		
		foreach ($_POST as $idx => $val) {
			echo "key : ".$idx." val : ".$val."<br>";
			if (strcmp(substr($idx, 0, 1), '_')!=0) {
				echo "--      key : ".$idx." val : ".$val."<br>";
				$data[$idx] = $val;
			} else {
				if (strcmp(substr($idx, 0, 2), '__')!=0) {
					$options[substr($idx, 1)] = $val;
				}
			}
		}
					
		foreach ($_FILES as $idx => $val) {
			if (!$val['error']) {
				$file_contents = file_get_contents($val['tmp_name']);
				$data[$idx] = $file_contents;
				
				if (isset($options['filename_'.$idx])) {
					$filename_field = $options['filename_'.$idx];
					$data[$filename_field] = $val['name']; 
				}
			}
		}
		
		foreach ($data as $idx => $val) {
			if (isset($options['null_'.$idx])) {
				$data[$idx] = null;
			}
		}
		
		$add_data['data'] = $data;
	}
	
	protected function process_add_process(&$add_data = NULL) {
		if (isset($add_data['data'])) {
			if (isset($add_data['options']['type']) && (strcasecmp($add_data['options']['type'], 'detail')==0)) {
				$metadata = $this->detail_model->get_metadata_from_query();
				$this->common->who_am_i($add_data['data'], $metadata['fields_list'], 'crea');
				$add_data['result'] = $this->detail_model->add($add_data['data']);
			} else {
				echo "senneh";
				$metadata = $this->model->get_metadata_from_query();
				$this->common->who_am_i($add_data['data'], $metadata['fields_list'], 'crea');
				$add_data['result'] = $this->model->add($add_data['data']);
			}
		}
	}
	
	protected function process_add_output(&$add_data = NULL) {
		if ($add_data['result']) {
			$this->common->flash_message('New record inserted', INFO);
		} else {
			$this->common->flash_message('Error inserting record', IMPORTANT);
		}
		redirect($this->referrer);
	}



	
	public function update() {
		$update_data = NULL;
		$this->update_input($update_data);
		$this->update_process($update_data);
		$this->update_output($update_data);
	}
	
	protected function update_input(&$update_data = NULL) {
		if (is_null($update_data)) $update_data = array();		
		$update_data['options'] = $this->common->get_options_from_uri();	
	}
	
	protected function update_process(&$update_data = NULL) {
		if (is_null($update_data)) $update_data = array();
		$this->populate_update_mode($update_data['mode'], $_ = array('update_data' => $update_data));		
		$this->populate_update_nav_data($update_data['nav_data']);
		$this->populate_update_content_data($update_data['content_data'], $_ = array('update_data' => $update_data));	
		$this->session->set_flashdata('referrer', $this->referrer);
	}

	protected function populate_update_nav_data(&$nav_data = NULL, &$external_data = NULL) {
		$this->populate_nav_data($nav_data, $external_data);
	}
	
	protected function populate_update_content_data(&$content_data = NULL, &$external_data = NULL) {
		$mode = $external_data['update_data']['mode'];

		$nav = $external_data['update_data']['nav_data'];

		$options = $external_data['update_data']['options'];		

		if (isset($options['type']) && (strcasecmp($options['type'], 'detail')==0)) {
			$data = $this->detail_model->get_single($options['keys']);
			// jika current = empty
			if (!$data['data']['current']) {
				// if previous != empty, current = previous
				if ($data['data']['previous']) {
					$data = $this->detail_model->get_single(array('id' => $data['data']['previous']['id']));
				}  else {	// else current = next
					$data = $this->detail_model->get_single(array('id' => $data['data']['next']['id']));
				}
			}
		} else {
			$data = $this->model->get_single($options['keys']);
			// jika current = empty
			if (!$data['data']['current']) {
				// if previous != empty, current = previous
				if ($data['data']['previous']) {
					$data = $this->model->get_single(array('id' => $data['data']['previous']['id']));
				}  else {	// else current = next
					$data = $this->model->get_single(array('id' => $data['data']['next']['id']));
				}
			}
		}
					
		$this->populate_update_actions($actions, $_ = array('mode' => $mode, 'content_data' => compact('nav', 'data', 'options', 'actions')));
		
		if (isset($options['type']) && (strcasecmp($options['type'], 'detail')==0)) {
			$mode .= '_detail';
		}
				
		$form = array('action' => get_unit_controller($this).'/process_'.$this->update_attr[$mode]['function'].'/'.$this->common->create_uri_record_part($options['keys']), 'attributes' => array());
		
		$content_data = compact('nav', 'options', 'data', 'actions', 'form');
	}
	
	protected function populate_update_mode(&$mode = NULL, &$external_data = NULL) {
		$mode = 'single';
	}
	
	protected function populate_update_actions(&$actions = NULL, &$external_data = NULL) {		
		$options = $external_data['content_data']['options'];
		if (isset($options['type']) && (strcasecmp($options['type'], 'detail')==0)) {
			$modifier = 'detail_';
		} else if (isset($options['type']) && (strcasecmp($options['type'], 'master')==0)) {
			$modifier = 'master_';
		} else {
			$modifier = '';
		}
		
		$metadata = $external_data['content_data']['data']['metadata'];
		$data = $external_data['content_data']['data']['data'];
		
		if (strcasecmp($external_data['mode'], 'single')==0) {
			$left = array();
			$center = array();
			$right = array();
			
			$left[] = $this->common->anchor(get_unit_controller($this).'/'.$external_data['content_data']['nav']['delete_single_'.$modifier.'function'].'/{RECORD}', '{LABEL_DELETE}', array('onclick' => 'return confirmDelete();'), '{LABEL_DISABLED_DELETE}');
			$left[] = $this->common->anchor(get_unit_controller($this).'/'.$external_data['content_data']['nav']['update_single_'.$modifier.'function'].'/{FIRST_RECORD}', '{LABEL_FIRST}').$this->common->anchor(get_unit_controller($this).'/'.$external_data['content_data']['nav']['update_single_'.$modifier.'function'].'/{PREV_RECORD}', '{LABEL_PREV}');
			
			$center[] = '{RECORD_ID}';
			
			$right[] = $this->common->anchor(get_unit_controller($this).'/'.$external_data['content_data']['nav']['update_single_'.$modifier.'function'].'/{NEXT_RECORD}', '{LABEL_NEXT}').$this->common->anchor(get_unit_controller($this).'/'.$external_data['content_data']['nav']['update_single_'.$modifier.'function'].'/{LAST_RECORD}', '{LABEL_LAST}');
			$right[] = '{SAVE_BUTTON}';
			$actions = compact('left', 'center', 'right');
		}
	}
	
	protected function update_output(&$update_data = NULL) {
		if (is_null($update_data)) $update_data = array();
	
		//required: $mode
		if (isset($update_data['mode'])) {
			$mode = $update_data['mode'];
	
			$application		= get_application($this);
			$application_title	= $this->common->get_application_description($application);
			$unit				= get_unit($this);
			$unit_title			= $this->common->get_unit_description($unit);
			$unit_controller	= get_unit_controller($this);
			$page 				= $this->update_attr[$mode]['title'];
			
			$metadata = compact('application', 'application_title', 'unit', 'unit_title', 'unit_controller', 'page');

			$nav_data 			= array_merge($metadata, $update_data['nav_data']);
			$content_data		= array_merge($metadata, $update_data['content_data']);
			
			$navigation			= $this->load->view($this->update_attr[$mode]['nav'], $nav_data, TRUE);
			$content			= $this->load->view($this->update_attr[$mode]['content'], $content_data, TRUE);;
		
			$data = array_merge($metadata, compact('navigation', 'content'));

			$this->load->view($this->update_attr[$mode]['layout'], $data);
		}
	}
	
	public function process_update() {
		$update_data = NULL;
		$this->process_update_input($update_data);
		$this->process_update_process($update_data);
		$this->process_update_output($update_data);
	}

	protected function process_update_input(&$update_data = NULL) {
		if (is_null($update_data)) $update_data = array();		
		$data = array();	
		$options = array();
		
		$update_data['options'] = $this->common->get_options_from_uri();	
		
		foreach ($_POST as $idx => $val) {
			if (strcmp(substr($idx, 0, 1), '_')!=0) {
				$data[$idx] = $val;
			} else {
				if (strcmp(substr($idx, 0, 2), '__')!=0) {
					$options[substr($idx, 1)] = $val;
				}
			}
		}
		
		foreach ($_FILES as $idx => $val) {
			if (!$val['error']) {
				$file_contents = file_get_contents($val['tmp_name']);
				$data[$idx] = $file_contents;
				
				if (isset($options['filename_'.$idx])) {
					$filename_field = $options['filename_'.$idx];
					$data[$filename_field] = $val['name']; 
				}
			}
		}
		
		foreach ($data as $idx => $val) {
			if (isset($options['null_'.$idx])) {
				$data[$idx] = null;
			}
		}
		
		$update_data['data'] = $data;
	}
	
	protected function process_update_process(&$update_data = NULL) {
		if (isset($update_data['data'])) {
			if (isset($update_data['options']['type']) && (strcasecmp($update_data['options']['type'], 'detail')==0)) {
				$metadata = $this->detail_model->get_metadata_from_query();
				$this->common->who_am_i($update_data['data'], $metadata['fields_list'], 'upd');
				$update_data['result'] = $this->detail_model->update($update_data['data'], $update_data['options']['keys']);
			} else {
				$metadata = $this->model->get_metadata_from_query();
				$this->common->who_am_i($update_data['data'], $metadata['fields_list'], 'upd');
				$update_data['result'] = $this->model->update($update_data['data'], $update_data['options']['keys']);
			}
		}
	}
	
	protected function process_update_output(&$update_data = NULL) {
		if ($update_data['result']) {
			$this->common->flash_message('Record updated', INFO);
		} else {
			$this->common->flash_message('Error updating record', IMPORTANT);
		}
		redirect($this->referrer);
	}



	public function delete() {
/*		$delete_data = NULL;
		$this->delete_input($delete_data);
		$this->delete_process($delete_data);
		$this->delete_output($delete_data);	*/
		$this->process_delete();
	}
	
	protected function delete_input(&$delete_data = NULL) {
	}
	
	protected function delete_process(&$delete_data = NULL) {
	}
	
	protected function delete_output(&$delete_data = NULL) {
	}

	
	public function process_delete() {
		$delete_data = NULL;
		$this->process_delete_input($delete_data);
		$this->process_delete_process($delete_data);
		$this->process_delete_output($delete_data);	
	}
	
	protected function process_delete_input(&$delete_data = NULL) {
		$options = $this->common->get_options_from_uri();
		$delete_data['options'] = $options;
		$delete_data['record'] = $options['keys'];
	}
	
	protected function process_delete_process(&$delete_data = NULL) {
		if (isset($delete_data['record'])) {
			if (isset($delete_data['options']['type']) && (strcasecmp($delete_data['options']['type'], 'detail')==0)) {
				$delete_data['result'] = $this->detail_model->delete($delete_data['record']);
			} else {
				$delete_data['result'] = $this->model->delete($delete_data['record']);
			}
		}
	}
	
	protected function process_delete_output(&$delete_data = NULL) {
		if ($delete_data['result']) {
			if (isset($delete_data['record']['id'])) {
				$record = '[ID: '.$delete_data['record']['id'].']';
			} else if (isset($delete_data['record']['key'])) {
				$record = '[KEY: '.$delete_data['record']['key'].']';
			} else {
				$record = '';
			}
			$this->common->flash_message('Record '.$record.' deleted', INFO);
		} else {
			$this->common->flash_message('Error deleting record', IMPORTANT);
		}
		redirect($this->referrer);
	}
	
	
		
	public function export() {
		/*$export_data = NULL;
		$this->export_input($export_data);
		$this->export_process($export_data);
		$this->export_output($export_data);*/
		$this->process_export();		
	}

	protected function export_input(&$export_data = NULL) {
	}
	
	protected function export_process(&$export_data = NULL) {
	}
	
	protected function export_output(&$export_data = NULL) {
	}
	
	public function process_export() {
		$export_data = NULL;
		$this->process_export_input($export_data);
		$this->process_export_process($export_data);
		$this->process_export_output($export_data);	
	}
	
	protected function process_export_input(&$export_data = NULL) {
	}
	
	protected function process_export_process(&$export_data = NULL) {				
		$result = $this->model->get_metadata_from_query();
		$fields = array();
		$fields_desc = $result['fields'];
		
		foreach ($fields_desc as $idx => $val) { // remove all autovalued fields
			if ($val['autovalue']) {
				unset($fields_desc[$idx]);
			} else {
				$fields[] = $val['name'];
			}
		}
		
		$_data = array();
		foreach ($fields_desc as $field) {
			if (is_null($field['size'])) {
				$_data[] = $field['type'];
			} else {
				$_data[] = $field['type'].'('.$field['size'].')';
			}
		}
		
		$data = array();
		$data[] = $_data;
		
		$export_data = compact('fields', 'data');
	}
	
	protected function process_export_output(&$export_data = NULL) {
	
		if (isset($export_data['filename'])) {
			$filename = $export_data['filename'];
		} else {
			$filename = NULL;
		}
	
		if (is_null($filename)) {
			$filename = $this->model_path;
			$filename .= '_'.now();
			$filename .= '_db.csv';
		}
		
		//print_r_pre($export_data);

		$file = generate_csv($export_data['fields'], $export_data['data']);
		force_download($filename, $file);
	}





	
	public function import() {
		$import_data = NULL;
		$this->import_input($import_data);
		$this->import_process($import_data);
		$this->import_output($import_data);
	}
	
	protected function import_input(&$import_data = NULL) {
	}

	protected function populate_import_nav_data(&$nav_data = NULL, &$external_data = NULL) {
		$this->populate_nav_data($nav_data, $external_data);
	}
	
	protected function import_process(&$import_data = NULL) {
		if (is_null($import_data)) $import_data = array();
		$this->populate_import_nav_data($import_data['nav_data']);
		//$this->populate_import_content_data($import_data['content_data'], $_ = array('import_data' => $import_data));	
	}
	
	protected function import_output(&$import_data = NULL) {
		$application		= get_application($this);
		$application_title	= $this->common->get_application_description($application);
		$unit				= get_unit($this);
		$unit_title			= $this->common->get_unit_description($unit);
		$unit_controller	= get_unit_controller($this);
		$page 				= $this->import_attr['title'];
		
		$metadata = compact('application', 'application_title', 'unit', 'unit_title', 'unit_controller', 'page');

		$nav_data 			= array_merge($metadata, $import_data['nav_data']);
		$content_data		= $metadata; //array_merge($metadata, $import_data['content_data']);
		
		$navigation			= $this->load->view($this->import_attr['nav'], $nav_data, TRUE);
		$content			= $this->load->view($this->import_attr['content'], $content_data, TRUE);;
	
		$data = array_merge($metadata, compact('navigation', 'content'));

		$this->load->view($this->import_attr['layout'], $data);
	}
	
	public function process_import() {
		$import_data = NULL;
		$this->process_import_input($import_data);
		$this->process_import_process($import_data);
		$this->process_import_output($import_data);	
	}
	
	protected function process_import_input(&$import_data = NULL) {	
		if (is_null($import_data)) $import_data = array();
		if (isset($_FILES['import_file']['tmp_name'])) {
			$import_data['import_file'] = $_FILES['import_file']['tmp_name'];
		}	
	}
	
	protected function process_import_process(&$import_data = NULL) {
		if (is_null($import_data)) $import_data = array();
		
		if (isset($import_data['import_file'])) {
			$row = 0;
			if (($handle = fopen($import_data['import_file'], "r")) !== FALSE) {

				$this->db->trans_begin();
				
				$error = false;
				
				while (($data = fgetcsv($handle)) !== FALSE) {
					$row++;
					
					if ($row == 1) {	// row 1 should contain field list
						$fields = $data;
					} else if ($row == 2) {					
						$type = $data;
					} else if ($row > 2) {
						if ($this->validate_import_data($data)) {
							$insert_data = array();
							foreach ($fields as $idx => $field) {
								if (!is_empty($idx) && !is_null_or_false($idx)) {
									$insert_data[$field] = $data[$idx];
								}
							}

							$this->common->who_am_i($insert_data, true, 'crea');
							$status = $this->model->add($insert_data);								
							
							if ($status === FALSE) {
								$error = true;
								break;
							}
						} else {
							$error = true;
							break;
						}
					}
				}
				
				if (($error) || ($this->db->trans_status() === FALSE)) {
					$import_data['error'] = true;
					$this->db->trans_rollback();					
				} else {
					$import_data['error'] = false;
					$import_data['n_rows'] = $row-2;
					$this->db->trans_commit();
				}
				
				$this->db->trans_off();
				
				fclose($handle);
			}		
		}
	}
	
	protected function validate_import_data($import_data = NULL) {
		return true;
	}
	
	protected function process_import_output(&$import_data = NULL) {
		if ($import_data['error']) {			
			$this->common->flash_message('Error inserting data, database rollbacked.', IMPORTANT);
		} else {
			$this->common->flash_message('Data successfully imported ('.$import_data['n_rows'].' records).', INFO);
		}
		redirect($this->referrer);
	}





	
	public function browse() {
		$browse_data = NULL;
		$this->browse_input($browse_data);
		$this->browse_process($browse_data);
		$this->browse_output($browse_data);
	}
	
	protected function browse_input(&$browse_data = NULL) {
		if (is_null($browse_data)) $browse_data = array();		
		$browse_data['options'] = $this->common->get_options_from_uri();
		if ($this->input->post('_criteria')) {
			$browse_data['criteria'] = $this->input->post('_criteria');
		}		
	}
	
	protected function browse_process(&$browse_data = NULL) {
		if (is_null($browse_data)) $browse_data = array();		
		$this->populate_browse_nav_data($browse_data['nav_data']);	
		$this->populate_browse_content_data($browse_data['content_data'], $_ = array('browse_data' => $browse_data));	
	}
	
	protected function populate_browse_content_data(&$content_data = NULL, &$external_data = NULL) {
		$options = $external_data['browse_data']['options'];
			
		if (isset($options['criteria'])) {
			$where = $options['criteria'];
		} else {
			$where = NULL;
		}
		
		if (isset($options['filter'])) {
			$filter = array();
			foreach ($options['filter'] as $idx => $val) {
				$vp = explode(" ", $val);
				if (in_arrayi($vp[0], array('=', '>', '>=', '<', '<=', 'LIKE'))) {
					$operator = $vp[0];
					unset($vp[0]);
					$vi = implode(" ", $vp);
					$filter[$idx.' '.$operator] = $vi;
				} else {
					$filter[$idx.' LIKE'] = $val.'%';
				}
			}
		
			if (!is_array($where)) {
				$where = array();
			}
			$where = array_merge($where, $filter);
			if (is_empty($where)) {
				$where = NULL;
			}
		}
		
		
		$records_per_page = $this->browse_attr['records_per_page'];
		if (is_null_or_false($options['page'])) {
			$options['page'] = 1;
		}
		$curr_page = $options['page'];			
		
		if (isset($options['order_by']['field']) && isset($options['order_by']['type'])) {
			$order_by = array($options['order_by']['field'] => $options['order_by']['type']);
		} else {
			$order_by = NULL;
		}
		
		$limit = $records_per_page;
		if (is_null_or_false($limit)) $limit = 0;
		
		if (isset($options['count'])) $limit = $options['count'];
		
		$offset = ($curr_page-1)*$limit;
		if (isset($options['from'])) {
			$start = $options['from'];
			$offset += $start;
		} else {
			$start = 0;
		}
		
		$data = $this->model->get_from_query(NULL, $where, array('offset' => $offset, 'limit' => $limit), $order_by, NULL, NULL, $options['table']);
		
		$n_records = $data['data']['n_all_records']-$start;			
		if ($limit>0) {
			$n_page = ceil($n_records/$limit);
		} else {
			$n_page = 1;
		}
		if ($n_page<1) $n_page = 1;
		$options['n_page'] = $n_page;		
		
		$this->populate_browse_actions($actions, $_ = array('content_data' => compact('data', 'options', 'actions')));
		
		$content_data = compact('options', 'data', 'actions');
	}	
	
	protected function populate_browse_nav_data(&$nav_data = NULL, &$external_data = NULL) {
		$this->populate_nav_data($nav_data, $external_data);
	}
	
	protected function populate_browse_actions(&$actions = NULL, &$external_data = NULL) {
		$metadata = $external_data['content_data']['data']['metadata'];
		$data = $external_data['content_data']['data']['data'];
	
		$left = array();
		$center = array();
		$right = array();
		
		$left[] = $this->common->anchor(get_unit_controller($this).'/'.$this->browse_attr['function'].'/{FIRST_PAGE}', '{LABEL_FIRST}').$this->common->anchor(get_unit_controller($this).'/'.$this->browse_attr['function'].'/{PREV_PAGE}', '{LABEL_PREV}');
		
		$center[] = '{CURR_PAGE} of {N_PAGE}';
		
		$right[] = $this->common->anchor(get_unit_controller($this).'/'.$this->browse_attr['function'].'/{NEXT_PAGE}', '{LABEL_NEXT}').$this->common->anchor(get_unit_controller($this).'/'.$this->browse_attr['function'].'/{LAST_PAGE}', '{LABEL_LAST}');
		
		$nav = compact('left', 'center', 'right');
		
		$left = array();
		$right = array();
		
		if ($data['n_records']>0) {
			foreach ($data['records'] as $ridx => $rval) {
				$right[$ridx][] = $left[$ridx][] = '<a href=\'javascript:void(0);\' onclick=\'browse_select("input_form", "'.$external_data['content_data']['options']['field'].'", "'.$rval[$external_data['content_data']['options']['ref_field']].'")\'>{LABEL_SELECT}</a>';
			}

			// bulk actions
			$bulk = array();
			$bulk['list'] = array();
			// example
			/*$bulk['processor'] = '';
			$bulk['list'][] = form_submit('__submit_'.$external_data['content_data']['nav']['delete_multiple_function'], $external_data['content_data']['nav']['delete_multiple_title']);*/

			// in_place actions
			$in_place = array();
			// example			
			/*$in_place[1][1] = anchor('test', '{VALUE}');*/
		}
		
		$actions = compact('left', 'right', 'bulk', 'in_place', 'nav');
	}	
	
	protected function browse_output(&$browse_data = NULL) {
		if (is_null($browse_data)) $browse_data = array();
	
		$application		= get_application($this);
		$application_title	= $this->common->get_application_description($application);
		$unit				= get_unit($this);
		$unit_title			= $this->common->get_unit_description($unit);
		$unit_controller	= get_unit_controller($this);
		$page 				= $this->browse_attr['title'];
		
		$metadata = compact('application', 'application_title', 'unit', 'unit_title', 'unit_controller', 'page');

		$content_data		= array_merge($metadata, $browse_data['content_data']);
		
		$content			= $this->load->view($this->browse_attr['content'], $content_data, TRUE);;
	
		$data = array_merge($metadata, compact('content'));

		$this->load->view($this->browse_attr['layout'], $data);

		
	}

	
	
	
	
	
	protected function save_as_csv($fields, $data, $filename = NULL) {
		if (is_null($filename)) {
			$filename = $this->model_path;
			$filename .= '_'.now();
			$filename .= '.csv';
		}

		$file = generate_csv($fields, $data);
		force_download($filename, $file); 
	}
	
	protected function save_as_pdf($fields, $data, $filename = NULL) {
		if (is_null($filename)) {
			$filename = $this->model_path;
			$filename .= '_'.now();
			$filename .= '.pdf';
		}
	
		$mpdf=new mPDF();
		$mpdf->debug = true;
		$mpdf->showImageErrors = true;		
		
		$mpdf->WriteHTML($this->load->view('report/tabular', array('title' => $filename, 'fields' => $fields, 'data' => $data), true));
		$mpdf->Output($filename, 'D');
	}
	
	protected function save_as_html($fields, $data, $filename = NULL) {
		$this->load->view('report/tabular', array('title' => $filename, 'fields' => $fields, 'data' => $data));
	}

	protected function save_as_xml($fields, $data, $filename = NULL) {
		if (is_null($filename)) {
			$filename = $this->model_path;
			$filename .= '_'.now();
			$filename .= '.xml';
		}

		$file = generate_xml($data);
		force_download($filename, $file); 
	}
	
	protected function populate_save_as_data(&$fields, &$data, $type, $filetype = NULL) {

		if (strcasecmp($type, 'full')==0) {
			$result = $this->model->get();
		} else {
			$options = $this->common->get_options_from_uri();
			$query = $options['query'];
			$result = $this->model->get_from_sql($query);
		}
		
		$data = $result['data']['records'];
		
		// encode binary file with base64
		foreach ($result['metadata']['fields'] as $ridx => $rval) {		
			$field_type = $rval['type'];
			if (strcasecmp($field_type, 'FILE')==0) {
				foreach ($data as $didx => $dval) {
					$data[$didx][$rval['name']] = 'BASE64:'.chunk_split(base64_encode($dval[$rval['name']]));
				}
			}
		}
		
		if (strcasecmp($type, 'full')==0) {
			$fields = $result['metadata']['fields_list'];
		} else {
			$fields = array();
			foreach ($result['metadata']['fields'] as $ridx => $rval) {
				if ($rval['viewable']) {
					$fields[] = $rval['name'];
				}
			}
			
			foreach ($data as $didx => $dval) {
				foreach ($dval as $_idx => $_val) {
					if (!in_array($_idx, $fields)) {
						unset($data[$didx][$_idx]);
					}
				}
			}					
		}
		
		foreach($fields as $idx => $val) {
			$fields[$idx] = $result['metadata']['fields_aliases'][$val];
		}

	}
	
	public function save_as($filetype, $type) {
		$save_as_data = compact('filetype', 'type');
		$this->save_as_input($save_as_data);
		$this->save_as_process($save_as_data);
		$this->save_as_output($save_as_data);
		
	}
	
	public function save_as_input(&$save_as_data = NULL) {
		$save_as_data['filetype'] = strtolower($save_as_data['filetype']);
	}
	
	public function save_as_process(&$save_as_data = NULL) {
		$this->populate_save_as_data($save_as_data['fields'], $save_as_data['data'], $save_as_data['type']);					
		$save_as_data['save_as'] = 'save_as_'.$save_as_data['filetype'];	
	}
	
	public function save_as_output(&$save_as_data = NULL) {
		if (method_exists($this, $save_as_data['save_as'])) {
			$this->$save_as_data['save_as']($save_as_data['fields'], $save_as_data['data']);
		}
	}




	
	public function reroute() {
	}
}

/* EOF controllers/gnc_mng.php */